﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Threading;
using PowersourceUIToolkit;
using UCL.Powersource.Analysis;
using UCL.Powersource.TFS;

namespace UCL.Powersource
{

    public class StatisticManagerQueue : PropertyChangedInitializedLifeCycle, IStatisticManagerQueue
    {


        //static StatisticManagerQueue _Default;

        //public static StatisticManagerQueue Default
        //{
        //    get
        //    {
        //        if (_Default != null && !_Default.IsDisposed) return _Default;
        //        _Default = new StatisticManagerQueue();
        //        _Default.Initialize();
        //        return _Default;
        //    }
        //}

        public StatisticManagerQueue()
        {
            BuildTimeOutMinutes = 20;
        }

        public ILog Log { get; private set; }
        public Dispatcher UIDispatcher { get; private set; }

        public ObservableCollection<string> LogActionMessages { get; private set; }
        public VerbosityLevels LogMessages_VerbosityLevel = VerbosityLevels.Debug;

        private void AddLogActionMessage_OnUIDispatcher(string a)
        {
            //if (LogActionMessages == null) LogActionMessages = new ObservableCollection<string>();
            UIDispatcher.BeginInvoke(new System.Action( () => { LogActionMessages.Add(a); }));
        }
        private void ClearLogActionMessages_OnUIDispatcher()
        {
            //if (LogActionMessages == null) LogActionMessages = new ObservableCollection<string>();
            UIDispatcher.BeginInvoke(new System.Action( () => {  LogActionMessages.Clear(); }));
        }


        private void InvokeOnUIDispatcher(System.Action callback)
        {
            UIDispatcher.Invoke(callback);
        }


        private void InvokeOnUIDispatcher(Delegate action, params object[] ea)
        {
            UIDispatcher.Invoke(action, ea);
        }

        protected override bool OnInitializing()
        {
            _WaitingStats = new Queue<StatisticManagerQueueItem>();
            _FinishedStats = new List<StatisticManagerQueueItem>();
            RemoteBuilderQueue = IoC.Get<IRemoteBuilderQueue>();
            Log = LogManager.GetLog(typeof(StatisticManagerQueue));
            LogActionMessages = new ObservableCollection<string>();


            UIDispatcher = Dispatcher.CurrentDispatcher;
            QueueThreadHasWork = new EventWaitHandle(false, EventResetMode.AutoReset);
            QueueThread = new Thread(new ThreadStart(QueueWorker));
            QueueThread.Name = "StatisticManagerQueueThread";
            QueueThread.Start();
            return true;
        }

        private EventWaitHandle QueueThreadHasWork;
        private Thread QueueThread;
        private Queue<StatisticManagerQueueItem> _WaitingStats;
        private bool FinishQueueThread;
        private List<StatisticManagerQueueItem> _FinishedStats;
        public IRemoteBuilderQueue RemoteBuilderQueue { get; private set; }
        public int BuildTimeOutMinutes { get; set; }
        Guid CurrentBatchQueueGuid;


        private void ConfigureProjectAnalysisBridgeForEvaluateForceRecalculate(ITfsTeamProjectConfiguration project)
        {
            var ab = project.ProjectAnalysisBridge as IEvaluateForceRecalculate;
            if (ab != null)
            {
                if (ab.EvaluateForceRecalculate == null)
                {
                    ab.EvaluateForceRecalculate = EvaluateForceRecalculateWhileInBatch;
                }
                else
                {
                    if (ab.EvaluateForceRecalculate != EvaluateForceRecalculateWhileInBatch)
                    {
                        throw new InvalidProgramException("EvaluateForceRecalculateWhileInBatch is already set to another statistic manager, internal error unable to continue");
                    }
                }
            }
        }

        private bool EvaluateForceRecalculateWhileInBatch(bool initialforceRecalculate, ITfsTeamProjectConfiguration teamProject, ITimeFrame tf)
        {
            if (!initialforceRecalculate) return false;
            if (this.CurrentBatchQueueGuid != Guid.Empty) return initialforceRecalculate;
            if (_FinishedStats == null) return initialforceRecalculate;
            lock (this)
            {
                var fs = _FinishedStats.FirstOrDefault(a => a.CurrentBatchQueueGuid == CurrentBatchQueueGuid);
                if (fs != null)
                {
                    Log.Debug("EvaluateForceRecalculateWhileInBatch switch force recalculate to false, because of already computed item {0}", fs.ToString());
                    return false;
                }

                else
                {
                    return true;
                }
            }
        }



        private void QueueWorker()
        {
            Log.DebugMethod("StatisticManager thread started.");

            StatisticManagerQueueItem statqueueItem = null;
            CurrentBatchQueueGuid = Guid.Empty;

            try
            {
                while (!FinishQueueThread)
                {
                    using (var LogActionMessagesLogger = new CallBackLog(LogMessages_VerbosityLevel, a =>
                    {
                        AddLogActionMessage_OnUIDispatcher(a);
                    }
                        ))
                    {
                        ClearLogActionMessages_OnUIDispatcher();
                        var cnt = 0;
                        lock (this)
                        {
                            cnt = _WaitingStats.Count;
                        }

                        if (CurrentBatchQueueGuid != Guid.Empty)
                        {
                            var oldBQguid = CurrentBatchQueueGuid;
                            CurrentBatchQueueGuid = Guid.NewGuid();
                            Log.Warn("CurrentBatchQueueGuid != null, Abandoning the batch {0} and starting new one {1}.", oldBQguid, CurrentBatchQueueGuid);
                        }
                        else
                        {
                            CurrentBatchQueueGuid = Guid.NewGuid();
                            Log.Trace("Starting new build batch {0}", CurrentBatchQueueGuid);
                        }


                        while (cnt > 0)
                        {

                            try
                            {
                                statqueueItem = _WaitingStats.Peek();
                                ConfigureProjectAnalysisBridgeForEvaluateForceRecalculate(statqueueItem.TfsTeamProjectConfiguration);
                                if (statqueueItem != null)
                                {
                                    CurrentAction = statqueueItem.GetActionString();
                                    statqueueItem.SetBatchQueueGuidForEnqueued(CurrentBatchQueueGuid);
                                    statqueueItem.Activated();
                                    CurrentAction = statqueueItem.GetActionString();
                                    RaiseStatisticManagerQueueUpdated_WithoutLock(statqueueItem, statqueueItem.Status, null, "Calculating");

                                    if (statqueueItem.Statistic.RequiresBuild)
                                    {
                                        if (RemoteBuilderQueue != null)
                                        {
                                            RaiseStatisticManagerQueueUpdated_WithoutLock(statqueueItem, statqueueItem.Status, null, "Building solution");
                                            bool buildFinished = false;
                                            var rbitem = RemoteBuilderQueue.GetBuildAsync
                                                (statqueueItem.TfsTeamProjectConfiguration, statqueueItem.TimeFrame,statqueueItem.ForceRecalculate,
                                                OnCompletedCallback:
                                                () => { buildFinished = true; }
                                                //OnChange: (sender, e) =>
                                                //    {
                                                //        RaiseStatisticManagerQueueUpdated_WithoutLock(statqueueItem, statqueueItem.Status, null, "Building solution - {0}", e.NewStatus);
                                                //    }
                                                );
                                            statqueueItem.RemoteBuilderQueueItem = rbitem;
                                            int activeWaitingLoop = 0;
                                            while (!buildFinished)
                                            {
                                                Thread.Sleep(100);
                                                activeWaitingLoop += 100;
                                                if (activeWaitingLoop > 60000 * BuildTimeOutMinutes)
                                                {
                                                    RemoteBuilderQueue.TerminateCurrentBuild();
                                                    var exc = new TimeoutException(string.Format("Build timeout [{0} min] for {1} reached! Terminating build.", BuildTimeOutMinutes, statqueueItem.ToString()));
                                                    Log.Error(exc);
                                                    throw exc;
                                                }
                                            }

                                        }
                                        else
                                        {
                                            RaiseStatisticManagerQueueUpdated_WithoutLock(statqueueItem, statqueueItem.Status, null, "Skipping build - queue not available.");
                                            Thread.Sleep(500);
                                        }
                                        //buildingItem.TfsTeamProjectConfiguration.ProjectAnalysisBridge.RetrieveCodeIndexConnectionString(buildingItem.TimeFrame, buildingItem.ForceRecalculate);
                                        //buildingItem.Statistic.Resolve(buildingItem.TfsTeamProjectConfiguration, buildingItem.TimeFrame, buildingItem.ForceRecalculate);
                                    }
                                    Log.Debug("Running resolve on : {0}", statqueueItem.ToString());
                                    statqueueItem.Statistic.Resolve(statqueueItem.TfsTeamProjectConfiguration, statqueueItem.TimeFrame, statqueueItem.ForceRecalculate);
                                    Log.Debug("Resolve finished on : {0}", statqueueItem.ToString());
                                    statqueueItem.Finish(null, true);
                                    cnt = Dequeue_Internal(statqueueItem);
                                    CurrentAction = statqueueItem.GetActionString();
                                    RaiseStatisticManagerQueueUpdated_WithoutLock(statqueueItem, statqueueItem.Status, null, "Finished");

                                }
                            }
                            catch (Exception exc)
                            {
                                try
                                {
                                    Log.Error(exc, "Calculating item {0} throws an exception.\r\n ", statqueueItem);
                                    if (statqueueItem.Status == QueuedItemStatus.Activated)
                                    {
                                        statqueueItem.Finish(exc, false);
                                        cnt = Dequeue_Internal(statqueueItem);
                                        CurrentAction = statqueueItem.GetActionString();
                                        RaiseStatisticManagerQueueUpdated_WithoutLock(statqueueItem, statqueueItem.Status, exc, "Finished with error");
                                    }
                                }
                                catch (Exception exc1)
                                {
                                    Log.Error(exc1, "Finishing item {0} which throw an exception throws another exception - details of original exception below:\r\n {1}", statqueueItem, exc.ToString());
#if DEBUG
                                    throw;
#endif
                                }

                            }

                        }
                        Log.DebugMethod("Nothing to work on in {0}, QueueThread is invoking WaitOne.", this.ToString());
                        IsCalculating_Recalculate_NonUICall();
                        Log.Debug("Finishing build batch {0}", CurrentBatchQueueGuid);
                        CurrentBatchQueueGuid = Guid.Empty;
                        QueueThreadHasWork.WaitOne(); //Wait for a signal that there is some work to do
                    }
                }
            }
            catch (ThreadAbortException)
            {
                if (statqueueItem != null)
                {

                    Log.Warn("{1} QueueThread abort. Abandoning bd {0}", statqueueItem.ToString(), this.ToString());
                    IsCalculating_Recalculate_NonUICall();
                }
            }
            catch (Exception exc)
            {
                Log.Error(exc, "Abnormal {0} QueueThread Termination.", this.ToString());
                IsCalculating_Recalculate_NonUICall();
                throw;
            }
        }

        private int Dequeue_Internal(StatisticManagerQueueItem buildingItem)
        {
            lock (this)
            {

                var item = _WaitingStats.Dequeue();
                Debug.Assert(item == buildingItem, "Internal error on dequeueing item {0}. It should be equal to statItem {1}", item.ToString(), buildingItem.ToString());
                _FinishedStats.Add(item);
            }
            WaitingStatsCount_Recalculate_NonUICall();
            FinishedStatsCount_Recalculate_NonUICall();
            int cnt = 0;
            lock (this)
            {
                cnt = _WaitingStats.Count();
            }
            if (cnt == 0) QueueThreadHasWork.Reset();
            return cnt;


        }


        protected override void OnDisposing()
        {
            FinishQueueThread = true;

            if (QueueThread != null && QueueThreadHasWork != null)
            {
                QueueThreadHasWork.Set();
                if (RemoteBuilderQueue != null) RemoteBuilderQueue.TerminateAllBuilds();

                for (int i = 0; i < 5; i++)
                {
                    QueueThreadHasWork.Set();
                    if (QueueThread.IsAlive)
                    {
                        Log.TraceMethod("{0} in Dispose: Needs to wait for QueueThread [retry {1}]", this.ToString(), i);
                        Thread.Sleep(200);
                    }
                }
                if (QueueThread.IsAlive)
                {
                    Log.Warn("{0} In Dispose: Timeout for QueueThread to finish normally reached. Requesting abort.", this.ToString());
                    QueueThread.Abort();
                }
                if (QueueThreadHasWork != null)
                {
                    QueueThreadHasWork.Dispose();
                    QueueThreadHasWork = null;
                }
                QueueThread = null;
            }
            else
            {
                if (!IsInitialized) return;
                if (Log != null) Log.Warn("{0} is in incoherent state. Aborting QueueThread if any and disposing QueueThreadHasWork");
                if (QueueThread != null)
                {
                    if (QueueThread.IsAlive) QueueThread.Abort();
                    QueueThread = null;
                }
                if (QueueThreadHasWork != null)
                {
                    QueueThreadHasWork.Dispose();
                    QueueThreadHasWork = null;
                }
            }

            // No thread safety here. Thread is either already finished or we destroy both lists and if background thread is still running it will be terminated abnormally
            if (_WaitingStats != null) { _WaitingStats.Clear(); _WaitingStats = null; }
            if (_FinishedStats != null) { _FinishedStats.Clear(); _FinishedStats = null; }

        }

        public event EventHandler<StatisticManagerQueueUpdatedEventArgs> StatisticManagerQueueUpdated;

        private void RaiseStatisticManagerQueueUpdated_InUIThread(StatisticManagerQueueUpdatedEventArgs ea)
        {
            Log.Debug("SM queue update: {0}", ea.ToString());
            if (StatisticManagerQueueUpdated != null)
            {
                StatisticManagerQueueUpdated(this, ea);
            }
        }

        internal protected void RaiseStatisticManagerQueueUpdated_WithoutLock(StatisticManagerQueueItem item, QueuedItemStatus status, Exception exc, string format, params object[] args)
        {
            // We create EventArgs in Non UI thread and then Dispatch it to UI thread - therefore values will never change in UI thread;
            StatisticManagerQueueUpdatedEventArgs ea = new StatisticManagerQueueUpdatedEventArgs(item, item.Status, exc, this, format, args);
            InvokeOnUIDispatcher(new Action<StatisticManagerQueueUpdatedEventArgs>(RaiseStatisticManagerQueueUpdated_InUIThread), ea);
        }

        public IStatisticManagerQueueItem GetStatisticQueueItem(IStatistic statistic, ITfsTeamProjectConfiguration teamProject, ITimeFrame timeframe, bool forceRecalculate)
        {
            if (!IsInitialized) Initialize();
            lock (this)
            {
                var ws = _WaitingStats.FirstOrDefault(a => a.Statistic == statistic && a.TfsTeamProjectConfiguration == teamProject && a.TimeFrame == timeframe);
                if (ws != null) return ws;
                var fs = _FinishedStats.FirstOrDefault(a => a.Statistic == statistic && a.TfsTeamProjectConfiguration == teamProject && a.TimeFrame == timeframe);
                return fs;
            }
        }

        public IStatisticManagerQueueItem EnqueueStatisticRecalculationAsync(IStatistic statistic, ITfsTeamProjectConfiguration teamProject, ITimeFrame timeframe, bool forceRecalculate)
        {
            if (!IsInitialized) Initialize();
            lock (this)
            {
                //First we try to find the statistic recalculation in waiting queue 
                var ws = _WaitingStats.FirstOrDefault(a => a.Statistic == statistic && a.TfsTeamProjectConfiguration == teamProject && a.TimeFrame == timeframe);
                if (ws != null)
                {
                    //if we are not already recalculating we can change force recalculate
                    if (ws.Status != QueuedItemStatus.Activated)
                    {
                        if (!ws.ForceRecalculate && forceRecalculate)
                        {
                            Log.Debug("Forcing to recalculate already enqueued statistic {0}", ws.ToString());

                            ws.SetForceRecalculateForInactive(true);
                        }
                        else
                        {
                            Log.Debug("Ignoring request to enqueue statistic. Enqueued item {0} found for {1} - {2} - {3} [recalculate={4}]", ws.ToString(), statistic.ToString(), teamProject.ToString(), timeframe.ToString(), forceRecalculate);
                        }
                    }
                    else
                    {
                        Log.Debug("Ignoring request to enqueue statistic. Activated item {0} found for {1} - {2} - {3} [recalculate={4}]", ws.ToString(), statistic.ToString(), teamProject.ToString(), timeframe.ToString(), forceRecalculate);
                    }
                    return ws;
                }

                //Now the logic is :
                // if does not exists, create and enqueue
                //if forceRecalculate and it is already finish we remove it from finsih and create new one
                // otherwise we just returned the finished one

                var fs = _FinishedStats.FirstOrDefault(a => a.Statistic == statistic && a.TfsTeamProjectConfiguration == teamProject && a.TimeFrame == timeframe);
                if (fs == null)
                {
                    return EnqueueStatistic_Internal(statistic, teamProject, timeframe, forceRecalculate);
                }
                else
                {
                    //if it was not required to recalculate return fs
                    if (!forceRecalculate) return fs;
                    //otherwise remove it from finished and start the new one
                    _FinishedStats.Remove(fs);
                    return EnqueueStatistic_Internal(statistic, teamProject, timeframe, forceRecalculate);
                }
            }

        }

        private StatisticManagerQueueItem EnqueueStatistic_Internal(IStatistic statistic, ITfsTeamProjectConfiguration teamProject, ITimeFrame timeframe, bool forceRecalculate)
        {
            if (!IsInitialized) Initialize();
            StatisticManagerQueueItem smqi = new StatisticManagerQueueItem(this, statistic, teamProject, timeframe, forceRecalculate);
            lock (this)
            {
                _WaitingStats.Enqueue(smqi);
                smqi.Enqueued();
            }
            IsCalculating_Recalculate();
            WaitingStatsCount_Recalculate();
            FinishedStatsCount_Recalculate();
            QueueThreadHasWork.Set();
            return smqi;
        }




        #region CurrentAction

        /// <summary>
        /// Field storage for property CurrentAction
        /// </summary>
        private string _CurrentAction = default(string);

        /// <summary>
        /// Property CurrentAction. TODODOC: Insert appropriate summary here
        /// </summary>
        public string CurrentAction
        {
            get { return _CurrentAction; }
            set { if (_CurrentAction == value) return; _CurrentAction = value; CurrentAction_RecalculateDependencies(); }
        }

        private void CurrentAction_RecalculateDependencies()
        {
            //Recalculate all properties here

            //NotifyOfPropertyChange is usually placed as last statement - first change all dependencies and then fire property change
            InvokeOnUIDispatcher(() => { NotifyOfPropertyChange(() => CurrentAction); });
        }

        //private void CurrentAction_Recalculate()
        //{
        //    _CurrentAction = default(string);
        //    //Insert your code here

        //    CurrentAction_RecalculateDependencies();
        //}

        #endregion


        #region IsCalculating

        /////// <summary>
        /////// Field storage for property IsCalculating
        /////// </summary>
        ////private bool _IsCalculating = default(bool);

        /// <summary>
        /// Property IsCalculating. TODODOC: Insert appropriate summary here
        /// </summary>
        public bool IsCalculating
        {
            get { return _WaitingStats != null && _WaitingStats.Count > 0; }
            //set { if (_IsCalculating == value) return; _IsCalculating = value; IsCalculating_RecalculateDependencies(); }
        }

        private void IsCalculating_RecalculateDependencies()
        {
            //Recalculate all properties here

            //NotifyOfPropertyChange is usually placed as last statement - first change all dependencies and then fire property change
            NotifyOfPropertyChange(() => IsCalculating);
        }

        private void IsCalculating_Recalculate()
        {
            IsCalculating_RecalculateDependencies();
        }

        private void IsCalculating_Recalculate_NonUICall()
        {
            try
            {
                InvokeOnUIDispatcher(IsCalculating_Recalculate);
            }
            catch (Exception exc)
            {
                if (Log != null) Log.Error(exc, "IsCalculating_Recalculate_NonUICall not successfull while trying to Invoke on UI thread");

            }
        }


        #endregion


        #region WaitingStatsCount

        /// <summary>
        /// Field storage for property WaitingStatsCount
        /// </summary>
        private int _WaitingStatsCount = default(int);

        /// <summary>
        /// Property WaitingStatsCount. TODODOC: Insert appropriate summary here
        /// </summary>
        public int WaitingStatsCount
        {
            get
            {
                return _WaitingStatsCount;

            }
            //set { if (_WaitingStatsCount == value) return; _WaitingStatsCount = value; WaitingStatsCount_RecalculateDependencies(); }
        }

        private void WaitingStatsCount_RecalculateDependencies()
        {
            //Recalculate all properties here

            //NotifyOfPropertyChange is usually placed as last statement - first change all dependencies and then fire property change
            NotifyOfPropertyChange(() => WaitingStatsCount);
        }

        private void WaitingStatsCount_Recalculate()
        {
            var cnt = 0;
            cnt = _WaitingStats == null ? 0 : _WaitingStats.Count;
            _WaitingStatsCount = cnt;
            WaitingStatsCount_RecalculateDependencies();
        }

        private void WaitingStatsCount_Recalculate_NonUICall()
        {
            InvokeOnUIDispatcher(WaitingStatsCount_Recalculate);
        }

        #endregion


        #region FinishedStatsCount

        /// <summary>
        /// Field storage for property FinishedStatsCount
        /// </summary>
        private int _FinishedStatsCount = default(int);

        /// <summary>
        /// Property FinishedStatsCount. TODODOC: Insert appropriate summary here
        /// </summary>
        public int FinishedStatsCount
        {
            get { return _FinishedStatsCount; }
            //set { if (_FinishedStatsCount == value) return; _FinishedStatsCount = value; FinishedStatsCount_RecalculateDependencies(); }
        }

        private void FinishedStatsCount_RecalculateDependencies()
        {
            NotifyOfPropertyChange(() => FinishedStatsCount);
        }

        private void FinishedStatsCount_Recalculate()
        {
            var cnt = 0;
            cnt = _FinishedStats == null ? 0 : _FinishedStats.Count;
            _FinishedStatsCount = cnt;
            FinishedStatsCount_RecalculateDependencies();
        }

        private void FinishedStatsCount_Recalculate_NonUICall()
        {
            InvokeOnUIDispatcher(FinishedStatsCount_Recalculate);
        }

        #endregion






        public bool IsCurrentThreadUIThread(Thread thread)
        {
            return UIDispatcher.Thread == thread;
        }
    }
}
